/*
 * Wazuh Vulnerability scanner - Scan Orchestrator
 * Copyright (C) 2015, Wazuh Inc.
 * May 1, 2023.
 *
 * This program is free software; you can redistribute it
 * and/or modify it under the terms of the GNU General Public
 * License (version 2) as published by the FSF - Free Software
 * Foundation.
 */

#ifndef _PACKAGE_SCANNER_HPP
#define _PACKAGE_SCANNER_HPP

#include "chainOfResponsability.hpp"
#include "databaseFeedManager.hpp"
#include "remediationDataCache.hpp"
#include "scanContext.hpp"
#include "scannerHelper.hpp"
#include "versionMatcher/versionMatcher.hpp"
#include <memory>
#include <unordered_set>
#include <variant>

auto constexpr DEFAULT_CNA {"nvd"};
auto constexpr L1_CACHE_SIZE {2048};

/**
 * @brief PackageScanner class.
 * This class is responsible for scanning the package and checking if it is vulnerable.
 * It receives the scan context and the database feed manager and returns the scan context with the vulnerability
 * details. The package format is used to determine the version object type or the version matcher strategy. The package
 * format can be deb, rpm, pypi, npm, pacman, snap, pkg, apk, win, macports. The vulnerability scan is performed using
 * the database feed manager.
 */
template<typename TDatabaseFeedManager = DatabaseFeedManager,
         typename TScanContext = ScanContext,
         typename TGlobalData = GlobalData,
         typename TRemediationDataCache = RemediationDataCache<>>
class TPackageScanner final : public AbstractHandler<std::shared_ptr<TScanContext>>
{
private:
    /**
     * @brief Package format to VersionObjectType / VersionMatcherStrategy map.
     *
     * @note The map is used to determine the version object type or the version matcher strategy based on the package
     * format.
     */
    std::unordered_map<std::string_view, std::variant<VersionObjectType, VersionMatcherStrategy>> m_packageMap {
        {"deb", VersionObjectType::DPKG},
        {"rpm", VersionObjectType::RPM},
        {"pypi", VersionObjectType::PEP440},
        {"npm", VersionObjectType::SemVer},
        {"pacman", VersionMatcherStrategy::Pacman},
        {"snap", VersionMatcherStrategy::Snap},
        {"pkg", VersionMatcherStrategy::PKG},
        {"apk", VersionMatcherStrategy::APK},
        {"win", VersionMatcherStrategy::Windows},
        {"macports", VersionMatcherStrategy::MacOS}};

    std::shared_ptr<TDatabaseFeedManager> m_databaseFeedManager;

    LRUCache<std::string, std::vector<TranslatedData>> m_translationL1Cache {L1_CACHE_SIZE};

    /**
     * @brief Scans a package for vulnerabilities using translation data from caches.
     *
     * This function scans a package for vulnerabilities by first searching for translation data in the Level 1 cache.
     * If translation data is found in the Level 1 cache, vulnerability scans are initiated based on the translations.
     * If translation data is not found in the Level 1 cache, the function searches the Level 2 cache.
     * If translation data is found in the Level 2 cache, vulnerability scans are initiated based on the translations.
     * If translation data is not found in either cache, the function logs a debug message.
     *
     * @param cnaName The name of the CVE Numbering Authorities (CNA) for vulnerability identification.
     * @param packageToTranslate The package data structure.
     * @param data The scan context containing package information and platform details.
     * @param vulnerabilityScan The function to perform vulnerability scans.
     * @return True if translations were found and vulnerability scans were initiated, false otherwise.
     *
     * @note The function assumes that the Level 1 cache (m_translationL1Cache) has been initialized and contains valid
     * data.
     */
    bool scanPackageTranslation(
        const std::string& cnaName,
        const PackageData& packageToTranslate,
        const std::shared_ptr<TScanContext> data,
        const std::function<bool(const std::string& cnaName,
                                 const PackageData& package,
                                 const NSVulnerabilityScanner::ScanVulnerabilityCandidate&)>& vulnerabilityScan)
    {
        // Generate cache key based on package details
        const auto osPlatform = std::string(data->osPlatform().data());

        // Search in Level 1 cache
        if (const auto cacheKey = osPlatform + "_" + packageToTranslate.vendor + "_" + packageToTranslate.name;
            m_translationL1Cache.isHit(cacheKey))
        {
            logDebug2(WM_VULNSCAN_LOGTAG,
                      "Translation for package '%s' in platform '%s' found in Level 1 cache.",
                      packageToTranslate.name.c_str(),
                      osPlatform.c_str());

            const auto L1Translations = m_translationL1Cache.getValue(cacheKey).value();

            // Retrieve translations from cache and initiate vulnerability scans
            for (const auto& translation : L1Translations)
            {
                auto translatedPackage {packageToTranslate};
                if (!translation.translatedProduct.empty())
                {
                    translatedPackage.name = translation.translatedProduct;
                }

                if (!translation.translatedVendor.empty())
                {
                    translatedPackage.vendor = translation.translatedVendor;
                }

                if (!translation.translatedVersion.empty())
                {
                    translatedPackage.version = translation.translatedVersion;
                }

                logDebug1(WM_VULNSCAN_LOGTAG,
                          "Initiating a vulnerability scan for package '%s' (%s) (%s) with CVE Numbering "
                          "Authorities (CNA) '%s' on Agent '%s' (ID: '%s', Version: '%s').",
                          translatedPackage.name.c_str(),
                          translatedPackage.format.c_str(),
                          translatedPackage.vendor.c_str(),
                          cnaName.c_str(),
                          data->agentName().data(),
                          data->agentId().data(),
                          data->agentVersion().data());

                m_databaseFeedManager->getVulnerabilitiesCandidates(cnaName, translatedPackage, vulnerabilityScan);
            }
            return true;
        }
        else
        {
            // Search in Level 2 cache
            if (const auto L2Translations = m_databaseFeedManager->getTranslationFromL2(packageToTranslate, osPlatform);
                !L2Translations.empty())
            {
                logDebug2(WM_VULNSCAN_LOGTAG,
                          "Translation for package '%s' in platform '%s' found in Level 2 cache.",
                          packageToTranslate.name.c_str(),
                          osPlatform.c_str());

                // Retrieve translations from cache and initiate vulnerability scans
                for (const auto& translation : L2Translations)
                {
                    auto translatedPackage {packageToTranslate};
                    if (!translation.translatedProduct.empty())
                    {
                        translatedPackage.name = translation.translatedProduct;
                    }

                    if (!translation.translatedVendor.empty())
                    {
                        translatedPackage.vendor = translation.translatedVendor;
                    }

                    if (!translation.translatedVersion.empty())
                    {
                        translatedPackage.version = translation.translatedVersion;
                    }

                    logDebug1(WM_VULNSCAN_LOGTAG,
                              "Initiating a vulnerability scan for package '%s' (%s) (%s) with CVE Numbering "
                              "Authorities (CNA) '%s' on Agent '%s' (ID: '%s', Version: '%s').",
                              translatedPackage.name.c_str(),
                              translatedPackage.format.c_str(),
                              translatedPackage.vendor.c_str(),
                              cnaName.c_str(),
                              data->agentName().data(),
                              data->agentId().data(),
                              data->agentVersion().data());

                    m_databaseFeedManager->getVulnerabilitiesCandidates(cnaName, translatedPackage, vulnerabilityScan);
                }
                // Store translations in Level 1 cache
                m_translationL1Cache.insertKey(cacheKey, std::move(L2Translations));
                return true;
            }
        }

        // If translations are not found in any cache, log a debug message and return false
        logDebug2(WM_VULNSCAN_LOGTAG,
                  "Translation for package '%s' in platform '%s' not found. Using provided packageName.",
                  packageToTranslate.name.c_str(),
                  osPlatform.c_str());
        return false;
    }

    /**
     * @brief Define what CNA will be used to read the data.
     *
     * @param ctx Scan context.
     * @return std::pair<std::string, std::string> CNA pair.
     */
    std::pair<std::string, std::string> getCNA(std::shared_ptr<TScanContext> ctx)
    {
        auto cnaName {m_databaseFeedManager->getCnaNameByFormat(ctx->packageFormat().data())};

        if (cnaName.empty())
        {
            cnaName = m_databaseFeedManager->getCnaNameBySource(ctx->packageSource().data());
            if (cnaName.empty())
            {
                cnaName =
                    m_databaseFeedManager->getCnaNameByPrefix(ctx->packageVendor().data(), ctx->osPlatform().data());
                if (cnaName.empty())
                {
                    cnaName = m_databaseFeedManager->getCnaNameByContains(ctx->packageVendor().data(),
                                                                          ctx->osPlatform().data());
                    if (cnaName.empty())
                    {
                        return {DEFAULT_CNA, DEFAULT_CNA};
                    }
                }
            }
        }
        const auto& mapping = TGlobalData::instance().cnaMappings();

        const auto& cnaMapping = mapping.at("cnaMapping");
        const auto platformEquivalence = [&](const std::string& platform) -> const std::string&
        {
            const auto& platformMapping = mapping.at("platformEquivalence");
            if (const auto it = platformMapping.find(platform); it == platformMapping.end())
            {
                return platform;
            }
            else
            {
                return it->template get_ref<const std::string&>();
            }
        };

        const auto majorVersionEquivalence = [&](const std::string& platform,
                                                 const std::string& majorVersion) -> const std::string&
        {
            const auto& majorVersionMapping = mapping.at("majorVersionEquivalence");
            if (const auto itPlatform = majorVersionMapping.find(platform); itPlatform == majorVersionMapping.end())
            {
                return majorVersion;
            }
            else
            {
                if (const auto itMajorVersion = itPlatform->find(majorVersion); itMajorVersion == itPlatform->end())
                {
                    return majorVersion;
                }
                else
                {
                    return itMajorVersion->template get_ref<const std::string&>();
                }
            }
        };

        if (const auto it = cnaMapping.find(cnaName); it == cnaMapping.end())
        {
            return {cnaName, cnaName};
        }
        else
        {
            std::string base = it->template get<std::string>();
            Utils::replaceAll(base, "$(PLATFORM)", platformEquivalence(ctx->osPlatform().data()));
            Utils::replaceAll(base,
                              "$(MAJOR_VERSION)",
                              majorVersionEquivalence(ctx->osPlatform().data(), ctx->osMajorVersion().data()));
            return {cnaName, base};
        }
    }

    bool platformVerify(const PackageData& package,
                        const NSVulnerabilityScanner::ScanVulnerabilityCandidate& callbackData,
                        std::shared_ptr<TScanContext> contextData)
    {
        // if the platforms are not empty, we need to check if the platform is in the list.
        if (callbackData.platforms())
        {
            bool matchPlatform {false};
            for (const auto& platform : *callbackData.platforms())
            {
                const std::string platformValue {platform->str()};
                // if the platform is a CPE, we need to parse it and check if the product is the same as the os
                // cpe.
                if (ScannerHelper::isCPE(platformValue))
                {
                    const auto cpe {ScannerHelper::parseCPE(platformValue)};
                    if (cpe.part.compare("o") == 0)
                    {
                        if (ScannerHelper::compareCPE(cpe, ScannerHelper::parseCPE(contextData->osCPEName().data())))
                        {
                            logDebug2(WM_VULNSCAN_LOGTAG,
                                      "The platform is in the list based on CPE comparison for "
                                      "Package: %s, Version: %s, CVE: %s, Content platform CPE: %s OS CPE: %s",
                                      package.name.c_str(),
                                      package.version.c_str(),
                                      callbackData.cveId()->str().c_str(),
                                      platformValue.c_str(),
                                      contextData->osCPEName().data());
                            matchPlatform = true;
                            break;
                        }
                    }
                }
                // If the platform is not a CPE, it is a string, at the moment, we only support the os code
                // name. This is used mainly for debian and ubuntu platforms.
                else
                {
                    if (platformValue.compare(contextData->osCodeName()) == 0)
                    {
                        logDebug2(WM_VULNSCAN_LOGTAG,
                                  "The platform is in the list based on OS code name comparison for "
                                  "Package: %s, Version: %s, CVE: %s, Content OS code name: %s, OS code name: %s",
                                  package.name.c_str(),
                                  package.version.c_str(),
                                  callbackData.cveId()->str().c_str(),
                                  platformValue.c_str(),
                                  contextData->osCodeName().data());
                        matchPlatform = true;
                        break;
                    }
                }
            }

            if (!matchPlatform)
            {
                logDebug2(WM_VULNSCAN_LOGTAG,
                          "The platform is not in the list for Package: %s, Version: %s, CVE: %s, OS CPE: %s, "
                          "OS code name: %s",
                          package.name.c_str(),
                          package.version.c_str(),
                          callbackData.cveId()->str().c_str(),
                          contextData->osCPEName().data(),
                          contextData->osCodeName().data());
                return false;
            }
        }

        return true;
    }

    bool vendorVerify(const PackageData& package,
                      const NSVulnerabilityScanner::ScanVulnerabilityCandidate& callbackData)
    {
        if (callbackData.vendor())
        {
            if (package.vendor.empty() || " " == package.vendor)
            {
                logDebug2(WM_VULNSCAN_LOGTAG,
                          "The vendor information is not available for Package: %s, Version: %s, "
                          "CVE: %s, Content vendor: %s",
                          package.name.c_str(),
                          package.version.c_str(),
                          callbackData.cveId()->str().c_str(),
                          callbackData.vendor()->str().c_str());
                return false;
            }
            else
            {
                if (package.vendor.compare(callbackData.vendor()->str()) != 0)
                {
                    logDebug2(WM_VULNSCAN_LOGTAG,
                              "The vendor is not the same for Package: %s, Version: %s, "
                              "CVE: %s, Content vendor: %s, Package vendor: %s",
                              package.name.c_str(),
                              package.version.c_str(),
                              callbackData.cveId()->str().c_str(),
                              callbackData.vendor()->str().c_str(),
                              package.vendor.c_str());
                    return false;
                }
                else
                {
                    logDebug2(WM_VULNSCAN_LOGTAG,
                              "Vendor match for Package: %s, Version: %s, "
                              "CVE: %s, Vendor: %s",
                              package.name.c_str(),
                              package.version.c_str(),
                              callbackData.cveId()->str().c_str(),
                              package.vendor.c_str());
                }
            }
        }

        return true;
    }

    bool versionMatch(const PackageData& package,
                      const NSVulnerabilityScanner::ScanVulnerabilityCandidate& callbackData,
                      std::shared_ptr<TScanContext> contextData)
    {
        std::variant<VersionObjectType, VersionMatcherStrategy> objectType = VersionMatcherStrategy::Unspecified;
        if (const auto it = m_packageMap.find(package.format); it != m_packageMap.end())
        {
            objectType = it->second;
        }
        const auto& packageVersion {package.version};
        auto packageVersionObject = VersionMatcher::createVersionObject(packageVersion, objectType);
        auto cveId = callbackData.cveId()->str();

        for (const auto& version : *callbackData.versions())
        {
            std::string versionString {version->version() ? version->version()->str() : ""};
            std::string versionStringLessThan {version->lessThan() ? version->lessThan()->str() : ""};
            std::string versionStringLessThanOrEqual {version->lessThanOrEqual() ? version->lessThanOrEqual()->str()
                                                                                 : ""};

            logDebug2(WM_VULNSCAN_LOGTAG,
                      "Scanning package - '%s' (Installed Version: %s, Security Vulnerability: %s). Identified "
                      "vulnerability: "
                      "Version: %s. Required Version Threshold: %s. Required Version Threshold (or Equal): %s.",
                      package.name.c_str(),
                      packageVersion.c_str(),
                      cveId.c_str(),
                      versionString.c_str(),
                      versionStringLessThan.c_str(),
                      versionStringLessThanOrEqual.c_str());

            // No version range specified, check if the installed version is equal to the required version.
            if (versionStringLessThan.empty() && versionStringLessThanOrEqual.empty())
            {
                if (VersionMatcher::compare(packageVersionObject, packageVersion, versionString, objectType) ==
                    VersionComparisonResult::A_EQUAL_B)
                {
                    // Version match found, the package status is defined by the vulnerability status.
                    if (version->status() == NSVulnerabilityScanner::Status::Status_affected)
                    {
                        logDebug1(WM_VULNSCAN_LOGTAG,
                                  "Match found, the package '%s', is vulnerable to '%s'. Current version: '%s' is "
                                  "equal to '%s'. - Agent '%s' (ID: '%s', Version: '%s').",
                                  package.name.c_str(),
                                  cveId.c_str(),
                                  packageVersion.c_str(),
                                  versionString.c_str(),
                                  contextData->agentName().data(),
                                  contextData->agentId().data(),
                                  contextData->agentVersion().data());

                        contextData->m_elements[cveId] = nlohmann::json::object();
                        contextData->m_matchConditions[cveId] = {std::move(versionString), MatchRuleCondition::Equal};

                        return true;
                    }

                    return false;
                }
            }
            else
            {
                // Version range specified

                // Check if the installed version satisfies the lower bound of the version range.
                auto lowerBoundMatch = false;
                if (versionString.compare("0") == 0)
                {
                    lowerBoundMatch = true;
                }
                else
                {
                    const auto matchResult =
                        VersionMatcher::compare(packageVersionObject, packageVersion, versionString, objectType);
                    lowerBoundMatch = matchResult == VersionComparisonResult::A_GREATER_THAN_B ||
                                      matchResult == VersionComparisonResult::A_EQUAL_B;
                }

                if (lowerBoundMatch)
                {
                    // Check if the installed version satisfies the upper bound of the version range.
                    auto upperBoundMatch = false;
                    if (!versionStringLessThan.empty() && versionStringLessThan.compare("*") != 0)
                    {
                        const auto matchResult = VersionMatcher::compare(
                            packageVersionObject, packageVersion, versionStringLessThan, objectType);
                        upperBoundMatch = matchResult == VersionComparisonResult::A_LESS_THAN_B;
                    }
                    else if (!versionStringLessThanOrEqual.empty())
                    {
                        const auto matchResult = VersionMatcher::compare(
                            packageVersionObject, packageVersion, versionStringLessThanOrEqual, objectType);
                        upperBoundMatch = matchResult == VersionComparisonResult::A_LESS_THAN_B ||
                                          matchResult == VersionComparisonResult::A_EQUAL_B;
                    }
                    else
                    {
                        upperBoundMatch = false;
                    }

                    if (upperBoundMatch)
                    {
                        // Version match found, the package status is defined by the vulnerability status.
                        if (version->status() == NSVulnerabilityScanner::Status::Status_affected)
                        {
                            logDebug1(WM_VULNSCAN_LOGTAG,
                                      "Match found, the package '%s', is vulnerable to '%s'. Current version: "
                                      "'%s' ("
                                      "less than '%s' or equal to '%s'). - Agent '%s' (ID: '%s', Version: '%s').",
                                      package.name.c_str(),
                                      cveId.c_str(),
                                      packageVersion.c_str(),
                                      versionStringLessThan.c_str(),
                                      versionStringLessThanOrEqual.c_str(),
                                      contextData->agentName().data(),
                                      contextData->agentId().data(),
                                      contextData->agentVersion().data());

                            contextData->m_elements[cveId] = nlohmann::json::object();

                            if (!versionStringLessThanOrEqual.empty())
                            {
                                contextData->m_matchConditions[cveId] = {std::move(versionStringLessThanOrEqual),
                                                                         MatchRuleCondition::LessThanOrEqual};
                            }
                            else
                            {
                                contextData->m_matchConditions[cveId] = {std::move(versionStringLessThan),
                                                                         MatchRuleCondition::LessThan};
                            }
                            return true;
                        }
                        else
                        {
                            logDebug2(WM_VULNSCAN_LOGTAG,
                                      "No match due to default status for Package: %s, Version: %s while scanning "
                                      "for Vulnerability: %s, "
                                      "Installed Version: %s, Required Version Threshold: %s, Required Version "
                                      "Threshold (or Equal): %s",
                                      package.name.c_str(),
                                      packageVersion.c_str(),
                                      cveId.c_str(),
                                      versionString.c_str(),
                                      versionStringLessThan.c_str(),
                                      versionStringLessThanOrEqual.c_str());

                            return false;
                        }
                    }
                }
            }
        }

        // No match found, the default status defines the package status.
        if (callbackData.defaultStatus() == NSVulnerabilityScanner::Status::Status_affected)
        {
            logDebug1(WM_VULNSCAN_LOGTAG,
                      "Match found, the package '%s' is vulnerable to '%s' due to default status. - Agent "
                      "'%s' (ID: '%s', Version: '%s').",
                      package.name.c_str(),
                      cveId.c_str(),
                      contextData->agentName().data(),
                      contextData->agentId().data(),
                      contextData->agentVersion().data());

            contextData->m_elements[cveId] = nlohmann::json::object();
            contextData->m_matchConditions[cveId] = {"", MatchRuleCondition::DefaultStatus};
            return true;
        }

        logDebug2(WM_VULNSCAN_LOGTAG,
                  "No match due to default status for Package: %s, Version: %s while scanning for Vulnerability: %s",
                  package.name.c_str(),
                  package.version.c_str(),
                  cveId.c_str());

        return false;
    }

    bool packageHotfixSolved(const PackageData& package,
                             const NSVulnerabilityScanner::ScanVulnerabilityCandidate& callbackData,
                             std::shared_ptr<TScanContext> contextData)
    {
        FlatbufferDataPair<NSVulnerabilityScanner::RemediationInfo> remediations {};
        m_databaseFeedManager->getVulnerabilityRemediation(callbackData.cveId()->str(), remediations);

        if (remediations.data == nullptr || remediations.data->updates() == nullptr ||
            remediations.data->updates()->size() == 0)
        {
            return false;
        }

        // Check that the agent has remediation data.
        auto agentRemediations = TRemediationDataCache::instance().getRemediationData(contextData->agentId().data());
        if (agentRemediations.hotfixes.empty())
        {
            logDebug2(
                WM_VULNSCAN_LOGTAG, "No remediations for agent '%s' have been found.", contextData->agentId().data());

            return false;
        }

        for (const auto& remediation : *(remediations.data->updates()))
        {
            // Check if the remediation is installed on the agent.
            if (agentRemediations.hotfixes.count(remediation->str()) != 0)
            {
                logDebug2(WM_VULNSCAN_LOGTAG,
                          "Remediation '%s' for package '%s' on agent '%s' that solves CVE '%s' has been found.",
                          remediation->str().c_str(),
                          package.name.c_str(),
                          contextData->agentId().data(),
                          callbackData.cveId()->str().c_str());

                contextData->m_elements.erase(callbackData.cveId()->str());
                contextData->m_matchConditions.erase(callbackData.cveId()->str());
                return true;
            }
        }

        logDebug2(WM_VULNSCAN_LOGTAG,
                  "No remediation for package '%s' on agent '%s' that solves CVE '%s' has been found.",
                  package.name.c_str(),
                  contextData->agentId().data(),
                  callbackData.cveId()->str().c_str());

        return false;
    }

    void packageScan(const std::shared_ptr<TScanContext>& data, const std::string& CNAValue)
    {
        auto vulnerabilityScan = [&](const std::string& cnaName,
                                     const PackageData& package,
                                     const NSVulnerabilityScanner::ScanVulnerabilityCandidate& callbackData)
        {
            try
            {
                // We don't override the vulnerability if it was detected before.
                auto cveId = callbackData.cveId()->str();
                if (data->m_elements.find(cveId) != data->m_elements.end())
                {
                    logDebug1(WM_VULNSCAN_LOGTAG,
                              "CVE '%s' already found by a higher priority CNA. Skipping.",
                              cveId.c_str());
                    return true;
                }

                /* Preliminary verifications before version matching. We return if the basic conditions are not met. */

                // If the candidate contains platforms, verify if agent OS is in the list.
                if (!platformVerify(package, callbackData, data))
                {
                    return false;
                }

                // If the candidate contains a vendor, verify if package vendor matches.
                if (!vendorVerify(package, callbackData))
                {
                    return false;
                }

                /* Real version analysis of the candidate. */
                if (versionMatch(package, callbackData, data))
                {
                    data->m_cnaDetectionSource[cveId] = cnaName;

                    // The candidate version matches the package. Post-match filtering.
                    if (data->osPlatform().compare("windows") == 0)
                    {
                        if (packageHotfixSolved(package, callbackData, data))
                        {
                            // An installed hotfix solves the vulnerability.
                            return false;
                        }
                    }

                    return true;
                }

                /* The candidate for this CVE is discarded. */
                return false;
            }
            catch (const std::exception& e)
            {
                // Log the warning and continue with the next vulnerability.
                logDebug1(WM_VULNSCAN_LOGTAG,
                          "Failed to scan package: '%s', CVE Numbering Authorities (CNA): '%s', Error: '%s'",
                          package.name.c_str(),
                          cnaName.c_str(),
                          e.what());

                return false;
            }
        };

        try
        {
            // Grouping all package related data in one single structure
            PackageData package = {.name = data->packageName().data(),
                                   .vendor = data->packageVendor().data(),
                                   .format = data->packageFormat().data(),
                                   .version = data->packageVersion().data()};

            if (!scanPackageTranslation(CNAValue, package, data, vulnerabilityScan))
            {
                package.name = Utils::toLowerCase(std::string(data->packageName()));
                package.vendor = Utils::toLowerCase(std::string(data->packageVendor()));

                logDebug1(WM_VULNSCAN_LOGTAG,
                          "Initiating a vulnerability scan for package '%s' (%s) (%s) with CVE Numbering "
                          "Authorities (CNA) "
                          "'%s' on Agent "
                          "'%s' (ID: '%s', Version: '%s').",
                          package.name.c_str(),
                          package.format.c_str(),
                          package.vendor.c_str(),
                          CNAValue.c_str(),
                          data->agentName().data(),
                          data->agentId().data(),
                          data->agentVersion().data());

                m_databaseFeedManager->getVulnerabilitiesCandidates(CNAValue, package, vulnerabilityScan);
            }
        }
        catch (const std::exception& e)
        {
            logWarn(WM_VULNSCAN_LOGTAG,
                    "Failed to scan package: '%s', CVE Numbering Authorities (CNA): '%s', Error: '%s'.",
                    data->packageName().data(),
                    CNAValue.c_str(),
                    e.what());
        }
    }

public:
    // LCOV_EXCL_START
    /**
     * @brief PackageScanner constructor.
     *
     * @param databaseFeedManager Database feed manager.
     */
    explicit TPackageScanner(std::shared_ptr<TDatabaseFeedManager>& databaseFeedManager)
        : m_databaseFeedManager(databaseFeedManager)
    {
    }
    // LCOV_EXCL_STOP

    /**
     * @brief Handles request and passes control to the next step of the chain.
     *
     * @param data Scan context.
     * @return std::shared_ptr<TScanContext> Abstract handler.
     */
    std::shared_ptr<TScanContext> handleRequest(std::shared_ptr<TScanContext> data) override
    {
        data->m_vulnerabilitySource = getCNA(data);

        const auto& CNAValue = data->m_vulnerabilitySource.second;

        if (DEFAULT_CNA != CNAValue)
        {
            packageScan(data, CNAValue);
        }
        else
        {
            // If the default CNA is used, we verify the CNA priority array
            if (TGlobalData::instance().vendorMaps().contains(ADP_DEFAULT_ARRAY_KEY))
            {
                auto& defaultCNAsArray = TGlobalData::instance().vendorMaps().at(ADP_DEFAULT_ARRAY_KEY);

                for (const nlohmann::json& cna : defaultCNAsArray)
                {
                    logDebug2(WM_VULNSCAN_LOGTAG, "Using CNA '%s' from CNA array.", cna.get<std::string>().c_str());
                    packageScan(data, cna);
                }
            }
            else
            {
                logDebug2(WM_VULNSCAN_LOGTAG, "No CNA array found, using default CNA '%s'.", DEFAULT_CNA);
                packageScan(data, DEFAULT_CNA);
            }
        }

        // Vulnerability scan ended for agent and package...
        logDebug1(WM_VULNSCAN_LOGTAG,
                  "Vulnerability scan for package '%s' on Agent '%s' has completed.",
                  data->packageName().data(),
                  data->agentId().data());

        if (data->m_elements.empty())
        {
            return nullptr;
        }
        return AbstractHandler<std::shared_ptr<TScanContext>>::handleRequest(std::move(data));
    }
};

using PackageScanner = TPackageScanner<>;

#endif // _PACKAGE_SCANNER_HPP
